﻿using System;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Mvc.Html;

namespace MvcApplication7
{
    public class ControllerEx : Controller
    {
        public ViewResult View<TModel>(IViewInfo<TModel> viewInfo, TModel model)
        {
            return View(viewInfo.ViewName, model);
        }
    }

    public interface IViewInfo<out TModel>
    {
        string ViewName { get; }
    }

    public static class Util
    {
        public const string Div1 = "div1";

        public static IViewInfo<TModel> CreateViewInfo<TModel>(string viewName)
        {
            return new ViewInfo<TModel>(viewName);
        }

        private class ViewInfo<TModel> : IViewInfo<TModel>
        {
            private readonly string viewName;

            public string ViewName
            {
                get { return viewName; }
            }

            public ViewInfo(string viewName)
            {
                this.viewName = viewName;
            }
        }

        public static void RenderPartial<TModel>(
            this HtmlHelper htmlHelper, 
            IViewInfo<TModel> viewInfo,
            TModel model)
        {
            htmlHelper.RenderPartial(viewInfo.ViewName, model);
        }

        public static void RenderPartial<TModel, TRenderModel>(
            this HtmlHelper htmlHelper,
            IScriptedViewInfo<TModel, TRenderModel> renderViewInfo,
            Func<TRenderModel, TModel> func)
        {
            htmlHelper.RenderPartial(
                renderViewInfo.RenderViewInfo.ViewInfo, 
                func(renderViewInfo.RenderViewInfo.RenderModel));
        }
    }

    public static class RenderViewInfo
    {
        public static RenderViewInfo<TModel, TRenderModel> Create<TModel, TRenderModel>(
            IViewInfo<TModel> viewInfo,
            TRenderModel renderModel)
        {
            return new RenderViewInfo<TModel, TRenderModel>(viewInfo, renderModel);
        }
    }

    public class RenderViewInfo<TModel, TRenderModel>
    {
        private readonly IViewInfo<TModel> viewInfo;
        private readonly TRenderModel renderModel;

        public RenderViewInfo(
            IViewInfo<TModel> viewInfo,
            TRenderModel renderModel)
        {
            this.viewInfo = viewInfo;
            this.renderModel = renderModel;
        }

        public IViewInfo<TModel> ViewInfo
        {
            get { return viewInfo; }
        }

        public TRenderModel RenderModel
        {
            get { return renderModel; }
        }
    }

    public class JsRenderer<TArg>
    {
        private readonly Action<TArg> selfAction;
        private readonly List<Action<TArg>> actions = new List<Action<TArg>>();

        public JsRenderer(Action<TArg> selfAction)
        {
            this.selfAction = selfAction;
        }

        public IScriptedViewInfo<TModel, TRenderModel> Add<TModel, TRenderModel>(
            RenderViewInfo<TModel, TRenderModel> renderViewInfo,
            Action<TRenderModel, TArg> action)
        {
            actions.Add(arg => action(renderViewInfo.RenderModel, arg));
            return new ScriptedViewInfo<TModel, TRenderModel>(renderViewInfo);
        }

        public void RenderJs(TArg arg)
        {
            selfAction(arg);
            foreach (var action in actions)
            {
                action(arg);
            }
        }

        private class ScriptedViewInfo<TModel, TRenderModel> : IScriptedViewInfo<TModel, TRenderModel>
        {
            private readonly RenderViewInfo<TModel, TRenderModel> renderViewInfo;

            public RenderViewInfo<TModel, TRenderModel> RenderViewInfo
            {
                get { return renderViewInfo; }
            }

            public ScriptedViewInfo(RenderViewInfo<TModel, TRenderModel> renderViewInfo)
            {
                this.renderViewInfo = renderViewInfo;
            }
        }
    }

    public interface IScriptedViewInfo<TModel, TRenderModel>
    {
        RenderViewInfo<TModel, TRenderModel> RenderViewInfo { get; }
    }
}